﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Routing;
using Orchard.ContentManagement;
using Orchard.Mvc;
using Orchard.UI.Navigation;
using SchoolPointe.Menus.Enums;
using SchoolPointe.RelatedContent.Drivers;
using SchoolPointe.RelatedContent.Models;
using SchoolPointe.RelatedContent.Services;
using SchoolPointe.Menus.Models;
using Orchard.ContentManagement.Records;
using SchoolPointe.Menus.Services;

namespace SchoolPointe.Menus.Drivers.RelatedContentDrivers
{
    public class MenuChildrenDriver : DefaultRelatedContentDriver
    {
        private readonly IContentManager _contentManager;
        private readonly IMenuService _menuService;
        private readonly INavigationManager _navigationManager;
        private readonly IHttpContextAccessor _httpContextAccessor;

        public MenuChildrenDriver(
            IRelatedContentService relatedContentService, 
            IContentManager contentManager, 
            IMenuService menuService,
            INavigationManager navigationManager,
            IHttpContextAccessor httpContextAccessor) 
            : base(relatedContentService, contentManager)
        {
            _contentManager = contentManager;
            _menuService = menuService;
            _navigationManager = navigationManager;
            _httpContextAccessor = httpContextAccessor;
        }

        public override string ContentType
        {
            get { return "Menu"; }
        }

        public override string RelationshipIdentifier
        {
            get { return "menu-items"; }
        }

        protected override Orchard.ContentManagement.Drivers.DriverResult Display(RelatedContentRelationship relationship, IEnumerable<ContentItemRecord> relatedItems, string displayType, dynamic shapeHelper)
        {
            if (displayType == "Detail")
            {
                var menuItems = _navigationManager.BuildMenu("BetterMenu." + this.ContentItem.Id.ToString());
                Stack<MenuItem> selectedPath = SetSelectedPath(menuItems, _httpContextAccessor.Current().Request.RequestContext.RouteData);
                dynamic menuShape = shapeHelper.Parts_BetterMenu();
                PopulateMenu(shapeHelper, menuShape, menuShape, menuItems);
                return ContentShape("Parts_BetterMenu", () => menuShape);
            }
            return null;
        }

        protected override Orchard.ContentManagement.Drivers.DriverResult Editor(RelatedContentRelationship relationship, IEnumerable<ContentItemRecord> relatedItems, IUpdateModel updater, dynamic shapeHelper)
        {
            //don't display menu item editor if the menu hasn't been created yet
            if (!this.ContentItem.IsPublished()) return null;

            var menuItemCreators = BuildMenuItemCreators(shapeHelper);
            var menuItemEditors = BuildMenuItemEditors(shapeHelper, relatedItems);
                      
            return ContentShape("Menu_Editor", () =>
                shapeHelper.Menu_Editor(
                    Creators: menuItemCreators,
                    MenuItems: menuItemEditors
                ));
        }

        private List<dynamic> BuildMenuItemCreators(dynamic shapeHelper)
        {
            var creators = new List<dynamic>();

            var customLink = _contentManager.New<BetterMenuItemPart>("MenuItem");
            customLink.MenuItemType = MenuItemType.CustomLink;
            customLink.SortOrder = 999;

            var contentItem = _contentManager.New<BetterMenuItemPart>("MenuItem");
            contentItem.MenuItemType = MenuItemType.ContentItem;
            contentItem.SortOrder = 999;
            var contentTypes = _menuService.GetPossibleContentTypes();

            creators.Add(shapeHelper.NestedEditor(
                Editor: _contentManager.BuildEditor(contentItem),
                ParentPrefix: Prefix + ".New.ContentItem",
                MenuId: this.ContentItem.Id,
                DisplayType: "CreateContentItemMenus",
                ContentTypeDefinitions: contentTypes));

            creators.Add(shapeHelper.NestedEditor(
                Editor: _contentManager.BuildEditor(customLink),
                ParentPrefix: Prefix + ".New.CustomLink",
                MenuId: this.ContentItem.Id,
                DisplayType: "Create"));

            return creators;
        }

        private List<dynamic> BuildMenuItemEditors(dynamic shapeHelper, IEnumerable<ContentItemRecord> relatedItems)
        {
            return relatedItems
                .Select(ri => _contentManager.Get<BetterMenuItemPart>(ri.Id))
                .OrderBy(mi => mi.SortOrder)
                .Select(menuItem =>
                    {
                        return shapeHelper.NestedEditor(
                            Editor: _contentManager.BuildEditor(menuItem),
                            ParentPrefix: Prefix + "." + menuItem.Id,
                            DisplayType: "Edit");
                    })
                .ToList();      
        }

        /// <summary>
        /// Populates the menu shapes.
        /// </summary>
        /// <param name="shapeFactory">The shape factory.</param>
        /// <param name="parentShape">The menu parent shape.</param>
        /// <param name="menu">The menu shape.</param>
        /// <param name="menuItems">The current level to populate.</param>
        protected void PopulateMenu(dynamic shapeFactory, dynamic parentShape, dynamic menu, IEnumerable<MenuItem> menuItems)
        {
            foreach (MenuItem menuItem in menuItems)
            {
                dynamic menuItemShape = BuildMenuItemShape(shapeFactory, parentShape, menu, menuItem);

                if (menuItem.Items != null && menuItem.Items.Any())
                {
                    PopulateMenu(shapeFactory, menuItemShape, menu, menuItem.Items);
                }

                parentShape.Add(menuItemShape, menuItem.Position);
            }
        }

        /// <summary>
        /// Builds a menu item shape.
        /// </summary>
        /// <param name="shapeFactory">The shape factory.</param>
        /// <param name="parentShape">The parent shape.</param>
        /// <param name="menu">The menu shape.</param>
        /// <param name="menuItem">The menu item to build the shape for.</param>
        /// <returns>The menu item shape.</returns>
        protected dynamic BuildMenuItemShape(dynamic shapeFactory, dynamic parentShape, dynamic menu, MenuItem menuItem)
        {
            var menuItemShape = shapeFactory.Parts_BetterMenuItem()
                .Text(menuItem.Text)
                .IdHint(menuItem.IdHint)
                .Href(menuItem.Href)
                .LinkToFirstChild(menuItem.LinkToFirstChild)
                .LocalNav(menuItem.LocalNav)
                .Selected(menuItem.Selected)
                .RouteValues(menuItem.RouteValues)
                .Item(menuItem)
                .Menu(menu)
                .Parent(parentShape);

            menuItemShape.Metadata.DisplayType = "Detail";

            foreach (var className in menuItem.Classes)
                menuItemShape.Classes.Add(className);

            return menuItemShape;
        }

        /// <summary>
        /// Identifies the currently selected path, starting from the selected node.
        /// </summary>
        /// <param name="menuItems">All the menuitems in the navigation menu.</param>
        /// <param name="currentRouteData">The current route data.</param>
        /// <returns>A stack with the selection path being the last node the currently selected one.</returns>
        protected static Stack<MenuItem> SetSelectedPath(IEnumerable<MenuItem> menuItems, RouteData currentRouteData)
        {
            if (menuItems == null)
                return null;

            foreach (MenuItem menuItem in menuItems)
            {
                Stack<MenuItem> selectedPath = SetSelectedPath(menuItem.Items, currentRouteData);
                if (selectedPath != null)
                {
                    menuItem.Selected = true;
                    selectedPath.Push(menuItem);
                    return selectedPath;
                }

                if (RouteMatches(menuItem.RouteValues, currentRouteData.Values))
                {
                    menuItem.Selected = true;

                    selectedPath = new Stack<MenuItem>();
                    selectedPath.Push(menuItem);
                    return selectedPath;
                }
            }

            return null;
        }

        /// <summary>
        /// Determines if a menu item corresponds to a given route.
        /// </summary>
        /// <param name="itemValues">The menu item.</param>
        /// <param name="requestValues">The route data.</param>
        /// <returns>True if the menu item's action corresponds to the route data; false otherwise.</returns>
        protected static bool RouteMatches(RouteValueDictionary itemValues, RouteValueDictionary requestValues)
        {
            if (itemValues == null && requestValues == null)
            {
                return true;
            }
            if (itemValues == null || requestValues == null)
            {
                return false;
            }
            if (itemValues.Keys.Any(key => requestValues.ContainsKey(key) == false))
            {
                return false;
            }
            return itemValues.Keys.All(key => string.Equals(Convert.ToString(itemValues[key]), Convert.ToString(requestValues[key]), StringComparison.OrdinalIgnoreCase));
        }
    }
}